// NeuralNetworkLab.cpp : Defines the entry point for the neural network application.
// Code by Cromwell D. Enage
// April 2009
#if defined(_UNICODE) && _MSC_VER < 1400
#error Please use Multi-Byte Character Set with Visual C++ 7.1
#endif

#define _CRT_SECURE_NO_DEPRECATE
#define _CRT_NON_CONFORMING_SWPRINTFS

#include <vld.h>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <fstream>
#include <iostream>
#include "NeuralNetworkLab.h"

#pragma comment(lib, "comctl32.lib")

NeuralNetworkGlobals* NeuralNetworkGlobals::instance_ = 0;

NeuralNetworkGlobals::NeuralNetworkGlobals() : flags_(0)
{
}

NeuralNetworkGlobals::~NeuralNetworkGlobals()
{
}

NeuralNetworkGlobals* NeuralNetworkGlobals::getInstance()
{
	if (instance_ == 0)
	{
		instance_ = new NeuralNetworkGlobals();
	}

	return instance_;
}

void NeuralNetworkGlobals::deleteInstance()
{
	if (instance_ != 0)
	{
		delete instance_;
		instance_ = 0;
	}
}

using namespace std;
using namespace fullsail_ai;

BOOL onOkCancelCommand(HWND dialog_handle, WPARAM w_param)
{
	switch (LOWORD(w_param))
	{
		case IDOK:
		case IDCANCEL:
		{
			EndDialog(dialog_handle, LOWORD(w_param));
			return TRUE;
		}
	}

	return FALSE;
}

// Message handler for about box.
INT_PTR CALLBACK aboutDialog(HWND dialog_handle, UINT message, WPARAM w_param, LPARAM l_param)
{
	UNREFERENCED_PARAMETER(l_param);

	switch (message)
	{
		case WM_INITDIALOG:
		{
			return static_cast<INT_PTR>(TRUE);
		}

		case WM_COMMAND:
		{
			return static_cast<INT_PTR>(onOkCancelCommand(dialog_handle, w_param));
		}
	}

	return static_cast<INT_PTR>(FALSE);
}

LRESULT CALLBACK windowProcedure(HWND window_handle, UINT message, WPARAM w_param, LPARAM l_param)
{
	switch (message)
	{
		case WM_COMMAND:
		{
			// Parse the menu selections:
			switch (LOWORD(w_param))
			{
				case IDM_APP_OPEN:
				{
					NeuralNetworkLab::getInstance()->onFileOpen(window_handle);
					break;
				}

				case IDM_APP_EXIT:
				{
					DestroyWindow(window_handle);
					break;
				}

				case IDM_APP_ABOUT:
				{
					DialogBox(
					    NeuralNetworkLab::getInstance()->getApplicationHandle()
					  , MAKEINTRESOURCE(IDD_ABOUTBOX)
					  , window_handle
					  , aboutDialog
					);
					break;
				}

				case IDM_NEURALNETWORK_RESET:
				{
					NeuralNetworkLab::getInstance()->onReset();
					break;
				}

				case IDM_NEURALNETWORK_RUN:
				{
					NeuralNetworkLab::getInstance()->onRun();
					break;
				}

				case IDM_NEURALNETWORK_STEP:
				{
					NeuralNetworkLab::getInstance()->onStep();
					break;
				}

				case IDM_NEURALNETWORK_EXPECTED_ACTION:
				{
					NeuralNetworkLab::getInstance()->onComboBox(HIWORD(w_param));
					break;
				}
			}

			break;
		}

		case WM_NOTIFY:
		{
			switch ((reinterpret_cast<LPNMHDR>(l_param))->code)
			{
				case TCN_SELCHANGING:
				{
					return FALSE;
				}

				case TCN_SELCHANGE:
				{
					NeuralNetworkLab::getInstance()->onTabSelection();
                    return TRUE;
				}
			}

			return NeuralNetworkLab::getInstance()->onListView(l_param);
		}

		case WM_KEYDOWN:
		{
			if (NeuralNetworkLab::getInstance()->onKeyPress(w_param))
			{
				return 0;
			}
			else
			{
				break;
			}
		}

		case WM_PAINT:
		{
			PAINTSTRUCT ps;

			NeuralNetworkLab::getInstance()->paintLabels(BeginPaint(window_handle, &ps));
			EndPaint(window_handle, &ps);
			break;
		}

		case WM_DESTROY:
		{
			PostQuitMessage(0);
			return 0;
		}
	}

	return DefWindowProc(window_handle, message, w_param, l_param);
}

// Message handler for outputs window.
LRESULT CALLBACK outputsProcedure(HWND window_handle, UINT message, WPARAM w_param, LPARAM l_param)
{
	switch (message)
	{
		case WM_KEYDOWN:
		{
			if (NeuralNetworkLab::getInstance()->onKeyPress(w_param))
			{
				return 0;
			}
			else
			{
				break;
			}
		}

		case WM_PAINT:
		{
			PAINTSTRUCT ps;

			NeuralNetworkLab::getInstance()->paintOutputs(BeginPaint(window_handle, &ps));
			EndPaint(window_handle, &ps);
			return 0;
		}

		case WM_SIZE:
		{
			NeuralNetworkLab::getInstance()->onSize(window_handle, w_param, l_param);
			return 0;
		}

		case WM_HSCROLL:
		{
			NeuralNetworkLab::getInstance()->onHScroll(window_handle, w_param, l_param);
			return 0;
		}

		case WM_VSCROLL:
		{
			NeuralNetworkLab::getInstance()->onVScroll(window_handle, w_param, l_param);
			return 0;
		}
	}

	return DefWindowProc(window_handle, message, w_param, l_param);
}

DWORD APIENTRY agentThread(LPVOID unused)
{
	NeuralNetworkLab::getInstance()->threadHandler();
	return 0;
}

NeuralNetworkLab* NeuralNetworkLab::instance_ = NULL;

NeuralNetworkLab::NeuralNetworkLab()
	: application_handle_(0)
	, training_sets_view_column_count_(1)
	, hidden_neurons_view_column_count_(2)
	, output_neurons_view_column_count_(2)
	, window_handle_(0)
	, tab_control_handle_(0)
	, expected_action_list_handle_(0)
	, training_sets_view_handle_(0)
	, inputs_view_handle_(0)
	, hidden_neurons_view_handle_(0)
	, output_neurons_view_handle_(0)
	, init_params_view_handle_(0)
	, open_button_handle_(0)
	, reset_button_handle_(0)
	, run_button_handle_(0)
	, step_button_handle_(0)
	, outputs_handle_(0)
	, outputs_device_context_handle_(0)
	, outputs_buffer_context_handle_(0)
	, outputs_buffer_bitmap_handle_(0)
	, pause_icon_handle_(0)
	, play_icon_handle_(0)
	, expected_action_label_x_(0)
	, expected_action_label_y_(0)
	, outputs_window_x_(0)
	, outputs_window_y_(68)
	, outputs_window_width_(768)
	, outputs_window_height_(0)
	, outputs_width_(0)
	, outputs_height_(0)
	, outputs_offset_()
	, cannot_close_thread_handle_(true)
	, should_stop_thread_handle_(false)
	, smith_sync_()
	, thread_sync_()
	, thread_id_(0)
	, thread_handle_(CreateThread(0, 0, agentThread, 0, CREATE_SUSPENDED, &thread_id_))
	, current_hidden_neuron_count_(3)
	, current_learning_factor_(0.1)
	, input_strings_()
	, action_strings_()
	, sample_inputs_()
	, expected_outputs_()
	, action_bitmap_handles_()
	, smith_()
	, current_inputs_()
	, current_example_index_(0)
	, iteration_cycle_(0)
{
	InitializeCriticalSection(&smith_sync_);
	InitializeCriticalSection(&thread_sync_);
	resetOffsets_();

	// This is a FOB's way to store a file filter string.
	text_filter_[0] = _T('T');
	text_filter_[1] = _T('e');
	text_filter_[2] = _T('x');
	text_filter_[3] = _T('t');
	text_filter_[4] = _T('\0');
	text_filter_[5] = _T('*');
	text_filter_[6] = _T('.');
	text_filter_[7] = _T('T');
	text_filter_[8] = _T('X');
	text_filter_[9] = _T('T');
	text_filter_[10] = _T('\0');
	text_filter_[11] = _T('\0');

	// Initialize the list-view item strings.
	_stprintf(hidden_neuron_count_param_text_, _T("%s"), _T("Hidden Neuron Count"));
	_stprintf(learning_factor_param_text_, _T("%s"), _T("Learning Factor"));
	_stprintf(select_me_text_, _T("%s"), _T("Select Me"));

	// Initialize the tooltip strings.
	_stprintf(open_button_text_, _T("%s"), _T("Open"));
	_stprintf(reset_button_text_, _T("%s"), _T("Reset"));
	_stprintf(run_button_text_, _T("%s"), _T("Run"));
	_stprintf(step_button_text_, _T("%s"), _T("Step"));
}

NeuralNetworkLab::~NeuralNetworkLab()
{
	EnterCriticalSection(&thread_sync_);
	should_stop_thread_handle_ = true;
	LeaveCriticalSection(&thread_sync_);

	while (cannot_close_thread_handle_)
	{
		Sleep(100);
	}

	DeleteObject(outputs_buffer_bitmap_handle_);
	DeleteDC(outputs_buffer_context_handle_);
	ReleaseDC(window_handle_, outputs_device_context_handle_);
	CloseHandle(thread_handle_);
	thread_handle_ = 0;
	thread_id_ = 0;
	removeBitmaps_();
	DeleteCriticalSection(&thread_sync_);
	DeleteCriticalSection(&smith_sync_);
}

NeuralNetworkLab* NeuralNetworkLab::getInstance()
{
	if (instance_ == 0)
	{
		instance_ = new NeuralNetworkLab();
	}

	return instance_;
}

void NeuralNetworkLab::deleteInstance()
{
	if (instance_)
	{
		delete instance_;
		instance_ = 0;
	}
}

void NeuralNetworkLab::removeBitmaps_()
{
	while (!action_bitmap_handles_.empty())
	{
		if (action_bitmap_handles_.back())
		{
			DeleteObject(action_bitmap_handles_.back());
		}

		action_bitmap_handles_.pop_back();
	}
}

void NeuralNetworkLab::resetOffsets_()
{
	outputs_offset_.x = outputs_offset_.y = 0;
}

void NeuralNetworkLab::stopRun_()
{
	SendMessage(
	    run_button_handle_
	  , BM_SETIMAGE
	  , IMAGE_ICON
	  , reinterpret_cast<LPARAM>(play_icon_handle_)
	);
}

bool NeuralNetworkLab::isReady_() const
{
	return !sample_inputs_.empty();
}

BOOL NeuralNetworkLab::shouldEnableButtons_() const
{
	return sample_inputs_.empty() ? FALSE : TRUE;
}

void NeuralNetworkLab::updateButtons_()
{
	BOOL should_enable = shouldEnableButtons_();

	EnableWindow(reset_button_handle_, should_enable);
	EnableWindow(run_button_handle_, should_enable);
	EnableWindow(step_button_handle_, should_enable);
}

void NeuralNetworkLab::updateOutputsWindowAndExpectedAction_()
{
	resetOffsets_();

	if (-1 == current_example_index_)
	{
		SendMessage(
		    expected_action_list_handle_
		  , CB_SETCURSEL
		  , static_cast<WPARAM>(smith_.getCurrentActionIndex())
		  , 0
		);
		EnableWindow(expected_action_list_handle_, TRUE);
	}
	else
	{
		EnableWindow(expected_action_list_handle_, FALSE);
		SendMessage(
		    expected_action_list_handle_
		  , CB_SETCURSEL
		  , static_cast<WPARAM>(
		        MlpAgent::computeIndexOfMaxOutput(expected_outputs_[current_example_index_])
		    )
		  , 0
		);
	}

	SCROLLINFO scroll_info;

	scroll_info.cbSize = sizeof(SCROLLINFO);
	scroll_info.fMask  = SIF_POS;
	scroll_info.nPos   = 0;
	MoveWindow(outputs_handle_, 0, 0, 0, 0, FALSE);
	MoveWindow(
	    outputs_handle_
	  , outputs_window_x_
	  , outputs_window_y_
	  , outputs_window_width_
	  , outputs_window_height_
	  , FALSE
	);
	SetScrollInfo(outputs_handle_, SB_HORZ, &scroll_info, TRUE);
	SetScrollInfo(outputs_handle_, SB_VERT, &scroll_info, TRUE);
	InvalidateRect(outputs_handle_, 0, FALSE);
}

void NeuralNetworkLab::updateEditableListViews_()
{
	InvalidateRect(inputs_view_handle_, 0, TRUE);
	InvalidateRect(init_params_view_handle_, 0, TRUE);
}

void NeuralNetworkLab::updateVisibleListViews_()
{
	if (NeuralNetworkGlobals::getInstance()->isFlagOn(NeuralNetworkGlobals::SHOW_NEURONS))
	{
		InvalidateRect(hidden_neurons_view_handle_, 0, FALSE);
		InvalidateRect(output_neurons_view_handle_, 0, FALSE);
	}
	else
	{
		InvalidateRect(training_sets_view_handle_, 0, FALSE);
	}
}

void NeuralNetworkLab::initializeAgent_()
{
	srand(1);
	smith_.initialize(
	    rand
	  , input_strings_.size()
	  , current_hidden_neuron_count_
	  , action_strings_.size()
	);

	assert(
	    (smith_.getCurrentOutputs().size() == action_strings_.size())
	 && "You did not resize the currentOutputs container correctly."
	);
	assert(
	    (smith_.getNetwork().getHiddenLayer().getNeurons().size() == current_hidden_neuron_count_)
	 && "Your MlpNetwork::initialize() method has violated its postcondition."
	);
	assert(
	    (
	        smith_.getNetwork().getHiddenLayer().getNeurons()[0].getWeights().size()
	     == input_strings_.size()
	    )
	 && "Either your Neuron::initialize() method or your NeuronLayer::initialize() method has violated its postcondition."
	);
	assert(
	    (
	        smith_.getNetwork().getOutputLayer().getNeurons()[0].getWeights().size()
	     == smith_.getNetwork().getHiddenLayer().getNeurons().size()
	    )
	 && "Review the bullet points in the \"Feed-Forward\" section of the \"Overview\" in your manual."
	);

	smith_.update(current_inputs_);
	iteration_cycle_ = 0;
}

void NeuralNetworkLab::initializeUserInterface_()
{
	initializeAgent_();

	// Clear out the drop-down list.
	SendMessage(expected_action_list_handle_, CB_RESETCONTENT, 0, 0);

	// Add the action strings to the drop-down list.
	for (size_t i = 0; i < action_strings_.size(); ++i)
	{
		SendMessage(
		    expected_action_list_handle_
		  , CB_ADDSTRING
		  , 0
		  , reinterpret_cast<LPARAM>(action_strings_[i].c_str())
		);
	}

	// Clear out all list views except for init_params_view_handle_.
	ListView_DeleteAllItems(training_sets_view_handle_);
	ListView_DeleteAllItems(inputs_view_handle_);
	ListView_DeleteAllItems(hidden_neurons_view_handle_);
	ListView_DeleteAllItems(output_neurons_view_handle_);

    // Initialize the LVCOLUMN structure.
    // The mask specifies that the format, width, text, and subitem members
    // of the structure are valid.
	LVCOLUMN list_view_column;

    list_view_column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
	list_view_column.cx = 96;
	list_view_column.fmt = LVCFMT_RIGHT;

	// Add the training_sets_view_handle_ columns.
	int columns_created_count = 1;

	for (size_t i = 0; i < input_strings_.size(); ++i)
	{
		list_view_column.iSubItem = columns_created_count;
		list_view_column.pszText = const_cast<TCHAR*>(input_strings_[i].c_str());

		if (columns_created_count == training_sets_view_column_count_)
		{
			ListView_InsertColumn(
			    training_sets_view_handle_
			  , columns_created_count
			  , &list_view_column
			);
			++training_sets_view_column_count_;
		}
		else
		{
			ListView_SetColumn(
			    training_sets_view_handle_
			  , columns_created_count
			  , &list_view_column
			);
		}

		++columns_created_count;
	}

	TCHAR outputs_column_text[MAX_LOADSTRING];

	list_view_column.cx = 64;
	list_view_column.fmt = LVCFMT_LEFT;
	list_view_column.iSubItem = columns_created_count;
	LoadString(
	    application_handle_
	  , IDS_LVC_ACTIONS
	  , outputs_column_text
	  , MAX_LOADSTRING
	);
	list_view_column.pszText = outputs_column_text;

	if (columns_created_count == training_sets_view_column_count_)
	{
		ListView_InsertColumn(
		    training_sets_view_handle_
		  , columns_created_count
		  , &list_view_column
		);
		++training_sets_view_column_count_;
	}
	else
	{
		ListView_SetColumn(
		    training_sets_view_handle_
		  , columns_created_count
		  , &list_view_column
		);
	}

	++columns_created_count;
	list_view_column.cx = 96;
	list_view_column.fmt = LVCFMT_RIGHT;

	for (size_t i = 0; i < action_strings_.size(); ++i)
	{
		list_view_column.iSubItem = columns_created_count;
		list_view_column.pszText = const_cast<TCHAR*>(
			action_strings_[i].c_str()
		);

		if (columns_created_count == training_sets_view_column_count_)
		{
			ListView_InsertColumn(
			    training_sets_view_handle_
			  , columns_created_count
			  , &list_view_column
			);
			++training_sets_view_column_count_;
		}
		else
		{
			ListView_SetColumn(
			    training_sets_view_handle_
			  , columns_created_count
			  , &list_view_column
			);
		}

		++columns_created_count;
    }

	// Remove excess training_sets_view_handle_ columns.
	while (columns_created_count < training_sets_view_column_count_)
	{
		ListView_DeleteColumn(training_sets_view_handle_, --training_sets_view_column_count_);
	}

	// Add the training_sets_view_handle_ items.
	LVITEM list_view_item;

	list_view_item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_STATE;
	list_view_item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
	list_view_item.lParam = 0;
	list_view_item.iItem = 0;

	for (size_t i = 0; i < sample_inputs_.size(); ++i)
	{
		list_view_item.state = 0;

		if (sample_inputs_[i] == current_inputs_)
		{
			list_view_item.state |= LVIS_SELECTED | LVIS_FOCUSED;
		}

//		list_view_item.iImage = list_view_item.iItem;
		list_view_item.iSubItem = 0;
		list_view_item.pszText = LPSTR_TEXTCALLBACK;
		ListView_InsertItem(training_sets_view_handle_, &list_view_item);

		for (size_t j = 0; j < sample_inputs_[i].size(); ++j)
		{
			++list_view_item.iSubItem;
			SendMessage(
			    training_sets_view_handle_
			  , LVM_SETITEM
			  , 0
			  , reinterpret_cast<LPARAM>(&list_view_item)
			);
		}

		++list_view_item.iSubItem;
		list_view_item.pszText = _T("");
		SendMessage(
		    training_sets_view_handle_
		  , LVM_SETITEM
		  , 0
		  , reinterpret_cast<LPARAM>(&list_view_item)
		);

		for (size_t j = 0; j < expected_outputs_[i].size(); ++j)
		{
			++list_view_item.iSubItem;
			list_view_item.pszText = LPSTR_TEXTCALLBACK;
			SendMessage(
			    training_sets_view_handle_
			  , LVM_SETITEM
			  , 0
			  , reinterpret_cast<LPARAM>(&list_view_item)
			);
		}

		++list_view_item.iItem;
	}

	// Add the inputs_view_handle_ items.
	list_view_item.stateMask = 0;
	list_view_item.state = 0;
	list_view_item.iItem = 0;

	for (size_t i = 0; i < input_strings_.size(); ++i)
	{
//		list_view_item.iImage = list_view_item.iItem;
		list_view_item.iSubItem = 0;
		list_view_item.pszText = LPSTR_TEXTCALLBACK;
		ListView_InsertItem(inputs_view_handle_, &list_view_item);
		list_view_item.iSubItem = 1;
		list_view_item.pszText = const_cast<TCHAR*>(input_strings_[i].c_str());
		SendMessage(
		    inputs_view_handle_
		  , LVM_SETITEM
		  , 0
		  , reinterpret_cast<LPARAM>(&list_view_item)
		);
		++list_view_item.iItem;
	}

	// Add the hidden_neurons_view_handle_ weight columns.
	MlpNetwork const& network = smith_.getNetwork();
	NeuronLayer const& hidden_layer = network.getHiddenLayer();
	vector<Neuron> const& hidden_neurons = hidden_layer.getNeurons();
	TCHAR buffer[MAX_BUFFER_SIZE];

	buffer[0] = _T('W');
	buffer[1] = _T('e');
	buffer[2] = _T('i');
	buffer[3] = _T('g');
	buffer[4] = _T('h');
	buffer[5] = _T('t');
	buffer[6] = _T(' ');
	list_view_column.cx = 80;
	columns_created_count = 2;

	for (size_t i = 0; i < hidden_neurons[0].getWeights().size(); ++i)
	{
		_stprintf(&buffer[7], _T("%u"), i);
		list_view_column.iSubItem = columns_created_count;
		list_view_column.pszText = buffer;

		if (columns_created_count == hidden_neurons_view_column_count_)
		{
			ListView_InsertColumn(
			    hidden_neurons_view_handle_
			  , columns_created_count
			  , &list_view_column
			);
			++hidden_neurons_view_column_count_;
		}
		else
		{
			ListView_SetColumn(
			    hidden_neurons_view_handle_
			  , columns_created_count
			  , &list_view_column
			);
		}

		++columns_created_count;
    }

	// Remove excess hidden_neurons_view_handle_ columns.
	while (columns_created_count < hidden_neurons_view_column_count_)
	{
		ListView_DeleteColumn(hidden_neurons_view_handle_, --hidden_neurons_view_column_count_);
	}

	// Add the hidden_neurons_view_handle_ items.
	list_view_item.iItem = 0;

	for (size_t i = 0; i < hidden_neurons.size(); ++i)
	{
//		list_view_item.iImage = list_view_item.iItem;
		list_view_item.iSubItem = 0;
		list_view_item.pszText = LPSTR_TEXTCALLBACK;
		ListView_InsertItem(hidden_neurons_view_handle_, &list_view_item);
		list_view_item.iSubItem = 1;
		list_view_item.pszText = LPSTR_TEXTCALLBACK;
		SendMessage(
		    hidden_neurons_view_handle_
		  , LVM_SETITEM
		  , 0
		  , reinterpret_cast<LPARAM>(&list_view_item)
		);

		for (size_t j = 0; j < hidden_neurons[i].getWeights().size(); ++j)
		{
			++list_view_item.iSubItem;
			list_view_item.pszText = LPSTR_TEXTCALLBACK;
			SendMessage(
			    hidden_neurons_view_handle_
			  , LVM_SETITEM
			  , 0
			  , reinterpret_cast<LPARAM>(&list_view_item)
			);
		}

		++list_view_item.iItem;
	}

	// Add the output_neurons_view_handle_ weight columns.
	NeuronLayer const& output_layer = network.getOutputLayer();
	vector<Neuron> const& output_neurons = output_layer.getNeurons();
	columns_created_count = 2;

	for (size_t i = 0; i < output_neurons[0].getWeights().size(); ++i)
	{
		_stprintf(&buffer[7], _T("%u"), i);
		list_view_column.iSubItem = columns_created_count;
		list_view_column.pszText = buffer;

		if (columns_created_count == output_neurons_view_column_count_)
		{
			ListView_InsertColumn(
			    output_neurons_view_handle_
			  , columns_created_count
			  , &list_view_column
			);
			++output_neurons_view_column_count_;
		}
		else
		{
			ListView_SetColumn(
			    output_neurons_view_handle_
			  , columns_created_count
			  , &list_view_column
			);
		}

		++columns_created_count;
    }

	// Remove excess output_neurons_view_handle_ columns.
	while (columns_created_count < output_neurons_view_column_count_)
	{
		ListView_DeleteColumn(output_neurons_view_handle_, --output_neurons_view_column_count_);
	}

	// Add the output_neurons_view_handle_ items.
	list_view_item.iItem = 0;

	for (size_t i = 0; i < output_neurons.size(); ++i)
	{
//		list_view_item.iImage = list_view_item.iItem;
		list_view_item.iSubItem = 0;
		list_view_item.pszText = LPSTR_TEXTCALLBACK;
		ListView_InsertItem(output_neurons_view_handle_, &list_view_item);
		list_view_item.iSubItem = 1;
		list_view_item.pszText = LPSTR_TEXTCALLBACK;
		SendMessage(
		    output_neurons_view_handle_
		  , LVM_SETITEM
		  , 0
		  , reinterpret_cast<LPARAM>(&list_view_item)
		);

		for (size_t j = 0; j < output_neurons[i].getWeights().size(); ++j)
		{
			++list_view_item.iSubItem;
			list_view_item.pszText = LPSTR_TEXTCALLBACK;
			SendMessage(
			    output_neurons_view_handle_
			  , LVM_SETITEM
			  , 0
			  , reinterpret_cast<LPARAM>(&list_view_item)
			);
		}

		++list_view_item.iItem;
	}
}

void NeuralNetworkLab::openFile_(HWND window_handle, TCHAR const* file_name)
{
#if defined(_UNICODE) && _MSC_VER < 1400
	char file_name_2003[MAX_BUFFER_SIZE];

	for (size_t i = 0; i < MAX_BUFFER_SIZE; ++i)
	{
		file_name_2003[i] = static_cast<char>(file_name[i]);
	}

	basic_ifstream<TCHAR> input_file_stream(file_name_2003);
#else
	basic_ifstream<TCHAR> input_file_stream(file_name);
#endif

	if (input_file_stream.good())
	{
		EnterCriticalSection(&smith_sync_);
		NeuralNetworkGlobals::getInstance()->turnOff(NeuralNetworkGlobals::SHOW_NETWORK_TRAINING);

		bool has_read = loadSamples(
		    input_file_stream
		  , input_strings_
		  , action_strings_
		  , sample_inputs_
		  , expected_outputs_
		);

		if (has_read)
		{
			removeBitmaps_();

			// Assume that the images associated with the actions
			// are in the same directory as the input file.
			basic_string<TCHAR> relative_path(file_name);
			size_t buffer_index = relative_path.length();

			while (buffer_index)
			{
				if (relative_path[--buffer_index] == _T('\\'))
				{
					relative_path = relative_path.substr(0, ++buffer_index);
					break;
				}
			}

			_stprintf(
			    title_bar_text_
			  , _T("%s - %s")
			  , title_text_
			  , file_name + buffer_index
			);
			SetWindowText(window_handle_, title_bar_text_);

			size_t character_index = 0;

			for (size_t i = 0; i < action_strings_.size(); ++i)
			{
				if (character_index < action_strings_[i].length())
				{
					character_index = action_strings_[i].length();
				}
			}

			LONG const CHAR_WIDTH = 8;
			LONG const CHAR_HEIGHT = 16;

			basic_string<TCHAR> filename_string;
			HBITMAP bitmap_handle;
			BITMAP bitmap;
			LONG max_width = static_cast<LONG>(character_index + 18) * CHAR_WIDTH;
			LONG max_height = 0;

			for (size_t i = 0; i < action_strings_.size(); ++i)
			{
				filename_string = relative_path + action_strings_[i];
				character_index = buffer_index;

				// Assume that each image's file name is the same as the name of the
				// corresponding action, but without whitespace.
				for (;;)
				{
					character_index = filename_string.find(_T(' '), character_index);

					if (character_index == basic_string<TCHAR>::npos)
					{
						break;
					}
					else
					{
						filename_string.erase(filename_string.begin() + character_index);
					}
				}

				filename_string.append(_T(".bmp"));

				if (
					bitmap_handle = reinterpret_cast<HBITMAP>(
						LoadImage(
						    application_handle_
						  , filename_string.c_str()
						  , IMAGE_BITMAP
						  , 0
						  , 0
						  , LR_DEFAULTCOLOR | LR_LOADFROMFILE
						)
					)
				)
				{
					GetObject(bitmap_handle, sizeof(BITMAP), &bitmap);

					if (max_width < bitmap.bmWidth)
					{
						max_width = bitmap.bmWidth;
					}

					if (max_height < bitmap.bmHeight)
					{
						max_height = bitmap.bmHeight;
					}

					action_bitmap_handles_.push_back(bitmap_handle);
				}
				else
				{
					action_bitmap_handles_.push_back(0);
				}
			}

			outputs_width_ = (max_width + CHAR_WIDTH) << 1;
			outputs_height_ = static_cast<LONG>(
				action_strings_.size() + 4
			) * CHAR_HEIGHT + max_height;
			current_inputs_ = sample_inputs_[current_example_index_ = 0];
			initializeUserInterface_();
		}

		LeaveCriticalSection(&smith_sync_);

		if (!has_read)
		{
			MessageBox(window_handle, _T("Invalid file."), _T("IO Error"), MB_OK);
			outputs_width_ = outputs_window_width_ - 32;
			outputs_height_ = outputs_window_height_ - 32;
		}

		updateButtons_();
		updateOutputsWindowAndExpectedAction_();
		updateEditableListViews_();
		updateVisibleListViews_();
		input_file_stream.close();
		input_file_stream.clear();
	}
}

BOOL NeuralNetworkLab::initializeApplication(HINSTANCE application_handle, int n_cmd_show)
{
	TCHAR app_class_string[MAX_LOADSTRING];
	INITCOMMONCONTROLSEX init_common_controls;

	init_common_controls.dwSize = sizeof(INITCOMMONCONTROLSEX);
	init_common_controls.dwICC = ICC_LISTVIEW_CLASSES | ICC_TAB_CLASSES;
	InitCommonControlsEx(&init_common_controls);

	LoadString(application_handle, IDS_APP_TITLE, title_text_, MAX_LOADSTRING);
	_stprintf(title_bar_text_, _T("%s"), title_text_);
	LoadString(application_handle, IDC_NEURALNETWORK, app_class_string, MAX_LOADSTRING);

	WNDCLASSEX window_class_ex;

	window_class_ex.cbSize = sizeof(WNDCLASSEX);
	window_class_ex.style = 0;
	window_class_ex.lpfnWndProc = windowProcedure;
	window_class_ex.cbClsExtra = 0;
	window_class_ex.cbWndExtra = 0;
	window_class_ex.hInstance = application_handle_ = application_handle;
	window_class_ex.hIcon = LoadIcon(application_handle, MAKEINTRESOURCE(IDI_FULLSAIL_AI));
	window_class_ex.hCursor = LoadCursor(0, IDC_ARROW);
	window_class_ex.hbrBackground = reinterpret_cast<HBRUSH>(GetStockObject(GRAY_BRUSH));
	window_class_ex.lpszMenuName = MAKEINTRESOURCE(IDC_NEURALNETWORK);
	window_class_ex.lpszClassName = app_class_string;
	window_class_ex.hIconSm = LoadIcon(application_handle, MAKEINTRESOURCE(IDI_FULLSAIL_AI));
	RegisterClassEx(&window_class_ex);

	if (
		window_handle_ = CreateWindow(
		    app_class_string
		  , title_bar_text_
		  , (
		        WS_OVERLAPPED
		      | WS_CAPTION
		      | WS_SYSMENU
		      | WS_MINIMIZEBOX
		    )
		  , CW_USEDEFAULT
		  , CW_USEDEFAULT
		  , 1024
		  , 768
		  , 0
		  , 0
		  , application_handle
		  , 0
		)
	)
	{
		LONG const TAB_HEIGHT = 28;
		TCHAR buffer[MAX_LOADSTRING];
		RECT client_rectangle;

		GetClientRect(window_handle_, &client_rectangle);
		client_rectangle.right -= client_rectangle.left;
		client_rectangle.bottom -= client_rectangle.top;
		client_rectangle.left = client_rectangle.top = 0;

		if (!(
			tab_control_handle_ = CreateWindow(
			    WC_TABCONTROL
			  , _T("")
			  , WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE
			  , 0
			  , 0
			  , client_rectangle.right
			  , TAB_HEIGHT
			  , window_handle_
			  , 0
			  , application_handle
			  , 0
			)
		))
		{
			MessageBox(
			    window_handle_
			  , _T("Creation Failed")
			  , _T("tab_control_handle_")
			  , MB_OK
			);
			DestroyWindow(window_handle_);
			return FALSE;
		}

		TCITEM tab_control_item;

		tab_control_item.mask = TCIF_TEXT | TCIF_IMAGE;
		tab_control_item.iImage = -1;
		tab_control_item.pszText = buffer;
		LoadString(application_handle, IDS_TAB_AGENT_VIEW, buffer, MAX_LOADSTRING);

		if (TabCtrl_InsertItem(tab_control_handle_, 0, &tab_control_item) == -1)
		{
			MessageBox(
			    window_handle_
			  , _T("Training Sets Tab Insertion Failed")
			  , _T("tab_control_handle_")
			  , MB_OK
			);
			DestroyWindow(window_handle_);
			return FALSE;
		}

		LoadString(application_handle, IDS_TAB_NEURAL_NETWORK_VIEW, buffer, MAX_LOADSTRING);

		if (TabCtrl_InsertItem(tab_control_handle_, 1, &tab_control_item) == -1)
		{
			MessageBox(
			    window_handle_
			  , _T("Neural Network View Tab Insertion Failed")
			  , _T("tab_control_handle_")
			  , MB_OK
			);
			DestroyWindow(window_handle_);
			return FALSE;
		}

		LONG const WINDOW_MARGIN = 24;
		LONG const BUTTON_SIZE = 52;
		LONG const WINDOW_TOP0 = WINDOW_MARGIN + TAB_HEIGHT;
		HICON icon_handle;

		if (
			open_button_handle_ = CreateWindow(
			    _T("BUTTON")
			  , open_button_text_
			  , WS_TABSTOP | WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON
			  , WINDOW_MARGIN
			  , WINDOW_TOP0
			  , BUTTON_SIZE
			  , BUTTON_SIZE
			  , window_handle_
			  , reinterpret_cast<HMENU>(IDM_APP_OPEN)
			  , application_handle
			  , 0
			)
		)
		{
			icon_handle = LoadIcon(application_handle, MAKEINTRESOURCE(IDI_OPEN));
			SendMessage(
			    open_button_handle_
			  , BM_SETIMAGE
			  , IMAGE_ICON
			  , reinterpret_cast<LPARAM>(icon_handle)
			);
			EnableWindow(open_button_handle_, TRUE);
		}
		else
		{
			MessageBox(
			    window_handle_
			  , _T("Creation Failed")
			  , _T("open_button_handle_")
			  , MB_OK
			);
			DestroyWindow(window_handle_);
			return FALSE;
		}

		if (
			reset_button_handle_ = CreateWindow(
			    _T("BUTTON")
			  , reset_button_text_
			  , WS_TABSTOP | WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON
			  , WINDOW_MARGIN + BUTTON_SIZE
			  , WINDOW_TOP0
			  , BUTTON_SIZE
			  , BUTTON_SIZE
			  , window_handle_
			  , reinterpret_cast<HMENU>(IDM_NEURALNETWORK_RESET)
			  , application_handle
			  , 0
			)
		)
		{
			icon_handle = LoadIcon(application_handle, MAKEINTRESOURCE(IDI_STOP));
			SendMessage(
			    reset_button_handle_
			  , BM_SETIMAGE
			  , IMAGE_ICON
			  , reinterpret_cast<LPARAM>(icon_handle)
			);
			EnableWindow(reset_button_handle_, FALSE);
		}
		else
		{
			MessageBox(
			    window_handle_
			  , _T("Creation Failed")
			  , _T("reset_button_handle_")
			  , MB_OK
			);
			DestroyWindow(window_handle_);
			return FALSE;
		}

		if (
			run_button_handle_ = CreateWindow(
			    _T("BUTTON")
			  , run_button_text_
			  , WS_TABSTOP | WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON
			  , WINDOW_MARGIN + (BUTTON_SIZE << 1)
			  , WINDOW_TOP0
			  , BUTTON_SIZE
			  , BUTTON_SIZE
			  , window_handle_
			  , reinterpret_cast<HMENU>(IDM_NEURALNETWORK_RUN)
			  , application_handle
			  , 0
			)
		)
		{
			pause_icon_handle_ = LoadIcon(application_handle, MAKEINTRESOURCE(IDI_PAUSE));
			play_icon_handle_ = LoadIcon(application_handle, MAKEINTRESOURCE(IDI_PLAY));
			SendMessage(
			    run_button_handle_
			  , BM_SETIMAGE
			  , IMAGE_ICON
			  , reinterpret_cast<LPARAM>(play_icon_handle_)
			);
			EnableWindow(run_button_handle_, FALSE);
		}
		else
		{
			MessageBox(
			    window_handle_
			  , _T("Creation Failed")
			  , _T("run_button_handle_")
			  , MB_OK
			);
			DestroyWindow(window_handle_);
			return FALSE;
		}

		if (
			step_button_handle_ = CreateWindow(
			    _T("BUTTON")
			  , step_button_text_
			  , WS_TABSTOP | WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON
			  , WINDOW_MARGIN + BUTTON_SIZE * 3
			  , WINDOW_TOP0
			  , BUTTON_SIZE
			  , BUTTON_SIZE
			  , window_handle_
			  , reinterpret_cast<HMENU>(IDM_NEURALNETWORK_STEP)
			  , application_handle
			  , 0
			)
		)
		{
			icon_handle = LoadIcon(application_handle, MAKEINTRESOURCE(IDI_PLAY_PAUSE));
			SendMessage(
			    step_button_handle_
			  , BM_SETIMAGE
			  , IMAGE_ICON
			  , reinterpret_cast<LPARAM>(icon_handle)
			);
			EnableWindow(step_button_handle_, FALSE);
		}
		else
		{
			MessageBox(
			    window_handle_
			  , _T("Creation Failed")
			  , _T("step_button_handle_")
			  , MB_OK
			);
			DestroyWindow(window_handle_);
			return FALSE;
		}

		LONG const WINDOW_RIGHT1 = client_rectangle.right - WINDOW_MARGIN;
		LONG const WINDOW_RIGHT0 = (WINDOW_RIGHT1 * 3) >> 3;

		expected_action_label_x_ = WINDOW_MARGIN * 2 + BUTTON_SIZE * 4;
		expected_action_label_y_ = WINDOW_TOP0;

		if (
			expected_action_list_handle_ = CreateWindow(
			    _T("COMBOBOX")
			  , 0
			  , (
			        WS_TABSTOP
			      | WS_CHILD
			      | WS_VISIBLE
			      | CBS_DROPDOWNLIST
			      | CBS_DISABLENOSCROLL
			    )
			  , expected_action_label_x_
			  , WINDOW_MARGIN + WINDOW_TOP0
			  , WINDOW_RIGHT0 - expected_action_label_x_
			  , BUTTON_SIZE << 2  // Max height of list during drop-down
			  , window_handle_
			  , reinterpret_cast<HMENU>(IDM_NEURALNETWORK_EXPECTED_ACTION)
			  , application_handle
			  , 0
			)
		)
		{
			EnableWindow(expected_action_list_handle_, FALSE);
		}
		else
		{
			MessageBox(
			    window_handle_
			  , _T("Creation Failed")
			  , _T("expected_action_text_handle_")
			  , MB_OK
			);
			DestroyWindow(window_handle_);
			return FALSE;
		}

		LONG const WINDOW_WIDTH1 = WINDOW_RIGHT1 - WINDOW_MARGIN;
		LONG const WINDOW_WIDTH0 = WINDOW_RIGHT0 - WINDOW_MARGIN;
		LONG const WINDOW_LEFT1 = WINDOW_RIGHT0 + WINDOW_MARGIN;
		LONG const WINDOW_BOTTOM4 = client_rectangle.bottom - WINDOW_MARGIN;
		LONG const WINDOW_HEIGHT1 = (WINDOW_BOTTOM4 >> 2) - TAB_HEIGHT;
		LONG const WINDOW_HEIGHT0 = (WINDOW_HEIGHT1 << 1) / 3;
		LONG const WINDOW_BOTTOM0 = WINDOW_TOP0 + BUTTON_SIZE;
		LONG const WINDOW_TOP1 = WINDOW_BOTTOM0 + WINDOW_MARGIN;
		LONG const WINDOW_BOTTOM1 = WINDOW_TOP1 + WINDOW_HEIGHT0;
		LONG const WINDOW_TOP2 = WINDOW_BOTTOM1 + WINDOW_MARGIN;
		LONG const WINDOW_BOTTOM2 = WINDOW_TOP2 + WINDOW_HEIGHT0;
		LONG const WINDOW_TOP3 = WINDOW_BOTTOM2 + WINDOW_MARGIN;
		LONG const WINDOW_BOTTOM3 = WINDOW_TOP3 + WINDOW_HEIGHT1;
		LONG const WINDOW_TOP4 = WINDOW_BOTTOM3 + WINDOW_MARGIN;

		LoadString(application_handle, IDS_LV_CURRENT_INPUTS, buffer, MAX_LOADSTRING);

		if (!(
			inputs_view_handle_ = CreateWindow(
			    WC_LISTVIEW
			  , buffer
			  , (
			        WS_CHILD
			      | WS_VISIBLE
			      | LVS_REPORT
			      | LVS_SINGLESEL
			      | LVS_EDITLABELS
			    )
			  , WINDOW_MARGIN
			  , WINDOW_TOP1
			  , WINDOW_WIDTH0
			  , WINDOW_HEIGHT0
			  , window_handle_
			  , 0
			  , application_handle
			  , 0
			)
		))
		{
			MessageBox(
			    window_handle_
			  , _T("Creation Failed")
			  , _T("inputs_view_handle_")
			  , MB_OK
			);
			DestroyWindow(window_handle_);
			return FALSE;
		}

		LoadString(application_handle, IDS_LV_PARAMETERS, buffer, MAX_LOADSTRING);

		if (!(
			init_params_view_handle_ = CreateWindow(
			    WC_LISTVIEW
			  , buffer
			  , (
			        WS_CHILD
			      | WS_VISIBLE
			      | LVS_REPORT
			      | LVS_SINGLESEL
			      | LVS_EDITLABELS
			    )
			  , WINDOW_MARGIN
			  , WINDOW_TOP2
			  , WINDOW_WIDTH0
			  , WINDOW_HEIGHT0
			  , window_handle_
			  , 0
			  , application_handle
			  , 0
			)
		))
		{
			MessageBox(
			    window_handle_
			  , _T("Creation Failed")
			  , _T("init_params_view_handle_")
			  , MB_OK
			);
			DestroyWindow(window_handle_);
			return FALSE;
		}

		LoadString(application_handle, IDS_LV_TRAINING_SETS, buffer, MAX_LOADSTRING);

		if (!(
			training_sets_view_handle_ = CreateWindow(
			    WC_LISTVIEW
			  , buffer
			  , (
			        WS_CHILD
			      | WS_VISIBLE
			      | LVS_REPORT
			      | LVS_SINGLESEL
//			      | LVS_SHOWSELALWAYS
			      | LVS_EX_DOUBLEBUFFER
			    )
			  , WINDOW_MARGIN
			  , WINDOW_TOP3
			  , WINDOW_WIDTH1
			  , (WINDOW_HEIGHT1 << 1) + WINDOW_MARGIN
			  , window_handle_
			  , 0
			  , application_handle
			  , 0
			)
		))
		{
			MessageBox(
			    window_handle_
			  , _T("Creation Failed")
			  , _T("training_sets_view_handle_")
			  , MB_OK
			);
			DestroyWindow(window_handle_);
			return FALSE;
		}

		LoadString(application_handle, IDS_LV_HIDDEN_NEURONS, buffer, MAX_LOADSTRING);

		if (!(
			hidden_neurons_view_handle_ = CreateWindow(
			    WC_LISTVIEW
			  , buffer
			  , WS_CHILD | LVS_REPORT | LVS_EX_DOUBLEBUFFER
			  , WINDOW_MARGIN
			  , WINDOW_TOP3
			  , WINDOW_WIDTH1
			  , WINDOW_HEIGHT1
			  , window_handle_
			  , 0
			  , application_handle
			  , 0
			)
		))
		{
			MessageBox(
			    window_handle_
			  , _T("Creation Failed")
			  , _T("hidden_neurons_view_handle_")
			  , MB_OK
			);
			DestroyWindow(window_handle_);
			return FALSE;
		}

		LoadString(application_handle, IDS_LV_OUTPUT_NEURONS, buffer, MAX_LOADSTRING);

		if (!(
			output_neurons_view_handle_ = CreateWindow(
			    WC_LISTVIEW
			  , buffer
			  , WS_CHILD | LVS_REPORT | LVS_EX_DOUBLEBUFFER
			  , WINDOW_MARGIN
			  , WINDOW_TOP4
			  , WINDOW_WIDTH1
			  , WINDOW_HEIGHT1
			  , window_handle_
			  , 0
			  , application_handle
			  , 0
			)
		))
		{
			MessageBox(
			    window_handle_
			  , _T("Creation Failed")
			  , _T("output_neurons_view_handle_")
			  , MB_OK
			);
			DestroyWindow(window_handle_);
			return FALSE;
		}

		COLORREF bg_color = RGB(0, 0, 0);
		COLORREF fg_color_0 = RGB(255, 255, 255);

		ListView_SetTextColor(training_sets_view_handle_, RGB(255, 0, 0));
		ListView_SetTextBkColor(training_sets_view_handle_, bg_color);
		ListView_SetBkColor(training_sets_view_handle_, bg_color);
		ListView_SetTextColor(hidden_neurons_view_handle_, fg_color_0);
		ListView_SetTextBkColor(hidden_neurons_view_handle_, bg_color);
		ListView_SetBkColor(hidden_neurons_view_handle_, bg_color);
		ListView_SetTextColor(output_neurons_view_handle_, fg_color_0);
		ListView_SetTextBkColor(output_neurons_view_handle_, bg_color);
		ListView_SetBkColor(output_neurons_view_handle_, bg_color);
		window_class_ex.lpfnWndProc = outputsProcedure;
		window_class_ex.cbWndExtra = sizeof(long);
		window_class_ex.hIcon = window_class_ex.hIconSm = 0;
		window_class_ex.hbrBackground = reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
		LoadString(application_handle, IDC_OUTPUTS, app_class_string, MAX_LOADSTRING);
		RegisterClassEx(&window_class_ex);

		if (!(
			outputs_handle_ = CreateWindow(
			    app_class_string
			  , 0
			  , WS_CHILD | WS_VISIBLE
			  , outputs_window_x_ = WINDOW_LEFT1
			  , outputs_window_y_ = WINDOW_TOP0
			  , outputs_window_width_ = WINDOW_RIGHT1 - WINDOW_LEFT1
			  , outputs_window_height_ = WINDOW_BOTTOM2 - WINDOW_TOP0
			  , window_handle_
			  , 0
			  , application_handle
			  , 0
			)
		))
		{
			MessageBox(
			    window_handle_
			  , _T("Creation Failed")
			  , _T("outputs_handle_")
			  , MB_OK
			);
			DestroyWindow(window_handle_);
			return FALSE;
		}

		LVCOLUMN list_view_column;

		list_view_column.mask = (
		    LVCF_FMT
		  | LVCF_WIDTH
		  | LVCF_TEXT
		  | LVCF_SUBITEM
		);
		list_view_column.cx = 64;
		list_view_column.fmt = LVCFMT_LEFT;
		list_view_column.iSubItem = 0;
		list_view_column.pszText = buffer;
		LoadString(application_handle, IDS_LVC_INPUTS, buffer, MAX_LOADSTRING);
		ListView_InsertColumn(
		    training_sets_view_handle_
		  , list_view_column.iSubItem
		  , &list_view_column
		);
		list_view_column.cx = 96;
		LoadString(application_handle, IDS_LVC_HIDDEN_NEURON, buffer, MAX_LOADSTRING);
		ListView_InsertColumn(
		    hidden_neurons_view_handle_
		  , list_view_column.iSubItem
		  , &list_view_column
		);
		LoadString(application_handle, IDS_LVC_OUTPUT_NEURON, buffer, MAX_LOADSTRING);
		ListView_InsertColumn(
		    output_neurons_view_handle_
		  , list_view_column.iSubItem
		  , &list_view_column
		);
		list_view_column.cx = 80;
		LoadString(application_handle, IDS_LVC_VALUE, buffer, MAX_LOADSTRING);
		ListView_InsertColumn(
		    inputs_view_handle_
		  , list_view_column.iSubItem
		  , &list_view_column
		);
		ListView_InsertColumn(
		    init_params_view_handle_
		  , list_view_column.iSubItem
		  , &list_view_column
		);
		list_view_column.cx = 160;
		list_view_column.iSubItem = 1;
		LoadString(application_handle, IDS_LVC_INPUT_NAME, buffer, MAX_LOADSTRING);
		ListView_InsertColumn(
		    inputs_view_handle_
		  , list_view_column.iSubItem
		  , &list_view_column
		);
		LoadString(application_handle, IDS_LVC_PARAM_NAME, buffer, MAX_LOADSTRING);
		ListView_InsertColumn(
		    init_params_view_handle_
		  , list_view_column.iSubItem
		  , &list_view_column
		);
		list_view_column.fmt = LVCFMT_RIGHT;
		list_view_column.cx = 80;
		LoadString(application_handle, IDS_LVC_BIAS, buffer, MAX_LOADSTRING);
		ListView_InsertColumn(
		    hidden_neurons_view_handle_
		  , list_view_column.iSubItem
		  , &list_view_column
		);
		ListView_InsertColumn(
		    output_neurons_view_handle_
		  , list_view_column.iSubItem
		  , &list_view_column
		);

		LVITEM list_view_item;

		list_view_item.mask = (
		    LVIF_TEXT
		  | LVIF_IMAGE
		  | LVIF_PARAM
		  | LVIF_STATE
		);
		list_view_item.state = 0;
		list_view_item.stateMask = 0;
		list_view_item.lParam = 0;
		list_view_item.iSubItem = 0;
		list_view_item.iItem = 0;
		list_view_item.pszText = LPSTR_TEXTCALLBACK;
		ListView_InsertItem(init_params_view_handle_, &list_view_item);
		list_view_item.iItem = 1;
		ListView_InsertItem(init_params_view_handle_, &list_view_item);

		outputs_device_context_handle_ = GetDC(outputs_handle_);
		outputs_buffer_context_handle_ = CreateCompatibleDC(outputs_device_context_handle_);
		outputs_buffer_bitmap_handle_ = CreateCompatibleBitmap(
		    outputs_device_context_handle_
		  , outputs_window_width_
		  , outputs_window_height_
		);
		_stprintf(buffer, _T("%s"), _T(".\\Data\\fps.txt"));
		openFile_(window_handle_, buffer);
		ShowWindow(window_handle_, n_cmd_show);
		ResumeThread(thread_handle_);
		UpdateWindow(window_handle_);

		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

void NeuralNetworkLab::run_()
{
	smith_.train(sample_inputs_, expected_outputs_, current_learning_factor_);
	smith_.update(current_inputs_);
	++iteration_cycle_;
}

void NeuralNetworkLab::threadHandler()
{
	HGDIOBJ old_bitmap_handle = SelectObject(
	    outputs_buffer_context_handle_
	  , outputs_buffer_bitmap_handle_
	);

	for (;;)
	{
		EnterCriticalSection(&thread_sync_);

		if (should_stop_thread_handle_)
		{
			SelectObject(outputs_buffer_context_handle_, old_bitmap_handle);
			cannot_close_thread_handle_ = false;
			LeaveCriticalSection(&thread_sync_);
			return;
		}

		LeaveCriticalSection(&thread_sync_);
		EnterCriticalSection(&smith_sync_);

		if (
			NeuralNetworkGlobals::getInstance()->isFlagOn(
				NeuralNetworkGlobals::SHOW_NETWORK_TRAINING
			)
		)
		{
			if (isReady_())
			{
				run_();
				BitBlt(
				    outputs_buffer_context_handle_
				  , 0
				  , 0
				  , outputs_window_width_
				  , outputs_window_height_
				  , outputs_buffer_context_handle_
				  , 0
				  , 0
				  , BLACKNESS
				);
				paintOutputs_(outputs_device_context_handle_, outputs_buffer_context_handle_);
				LeaveCriticalSection(&smith_sync_);
			}
			else
			{
				NeuralNetworkGlobals::getInstance()->turnOff(
					NeuralNetworkGlobals::SHOW_NETWORK_TRAINING
				);
				LeaveCriticalSection(&smith_sync_);
				stopRun_();
				InvalidateRect(outputs_handle_, 0, FALSE);
			}

			updateVisibleListViews_();
		}
		else
		{
			LeaveCriticalSection(&smith_sync_);
		}

		Sleep(25);
	}
}

void NeuralNetworkLab::onTabSelection()
{
	if (SendMessage(tab_control_handle_, TCM_GETCURSEL, 0, 0))
	{
		EnterCriticalSection(&smith_sync_);
		NeuralNetworkGlobals::getInstance()->turnOn(NeuralNetworkGlobals::SHOW_NEURONS);
		LeaveCriticalSection(&smith_sync_);
		ShowWindow(training_sets_view_handle_, SW_HIDE);
		ShowWindow(hidden_neurons_view_handle_, SW_SHOW);
		ShowWindow(output_neurons_view_handle_, SW_SHOW);
	}
	else
	{
		EnterCriticalSection(&smith_sync_);
		NeuralNetworkGlobals::getInstance()->turnOff(NeuralNetworkGlobals::SHOW_NEURONS);
		LeaveCriticalSection(&smith_sync_);
		ShowWindow(hidden_neurons_view_handle_, SW_HIDE);
		ShowWindow(output_neurons_view_handle_, SW_HIDE);
		ShowWindow(training_sets_view_handle_, SW_SHOW);
	}

	if (isReady_())
	{
		updateVisibleListViews_();
	}
}

void NeuralNetworkLab::onReset()
{
	EnterCriticalSection(&smith_sync_);
	NeuralNetworkGlobals::getInstance()->turnOff(NeuralNetworkGlobals::SHOW_NETWORK_TRAINING);
	initializeAgent_();
	LeaveCriticalSection(&smith_sync_);
	stopRun_();
	InvalidateRect(outputs_handle_, 0, FALSE);
	updateVisibleListViews_();
}

void NeuralNetworkLab::onRun()
{
	if (NeuralNetworkGlobals::getInstance()->isFlagOn(NeuralNetworkGlobals::SHOW_NETWORK_TRAINING))
	{
		EnterCriticalSection(&smith_sync_);
		NeuralNetworkGlobals::getInstance()->turnOff(NeuralNetworkGlobals::SHOW_NETWORK_TRAINING);
		LeaveCriticalSection(&smith_sync_);
		stopRun_();
		InvalidateRect(outputs_handle_, 0, FALSE);
		updateVisibleListViews_();
	}
	else
	{
		EnterCriticalSection(&smith_sync_);
		NeuralNetworkGlobals::getInstance()->turnOn(NeuralNetworkGlobals::SHOW_NETWORK_TRAINING);
		LeaveCriticalSection(&smith_sync_);
		SendMessage(
		    run_button_handle_
		  , BM_SETIMAGE
		  , IMAGE_ICON
		  , reinterpret_cast<LPARAM>(pause_icon_handle_)
		);
	}
}

void NeuralNetworkLab::onStep()
{
	EnterCriticalSection(&smith_sync_);
	NeuralNetworkGlobals::getInstance()->turnOff(NeuralNetworkGlobals::SHOW_NETWORK_TRAINING);
	run_();
	LeaveCriticalSection(&smith_sync_);
	stopRun_();
	InvalidateRect(outputs_handle_, 0, FALSE);
	updateVisibleListViews_();
}

bool NeuralNetworkLab::onKeyPress(WPARAM w_param)
{
	switch (w_param)
	{
		case VK_BACK:
		{
			EnterCriticalSection(&smith_sync_);

			if (isReady_())
			{
				onReset();
				LeaveCriticalSection(&smith_sync_);
				return true;
			}
			else
			{
				LeaveCriticalSection(&smith_sync_);
				break;
			}
		}

		case VK_SPACE:
		{
			EnterCriticalSection(&smith_sync_);

			if (isReady_())
			{
				onRun();
				LeaveCriticalSection(&smith_sync_);
				return true;
			}
			else
			{
				LeaveCriticalSection(&smith_sync_);
				break;
			}
		}

		case VK_ADD:
		{
			EnterCriticalSection(&smith_sync_);

			if (isReady_())
			{
				onStep();
				LeaveCriticalSection(&smith_sync_);
				return true;
			}
			else
			{
				LeaveCriticalSection(&smith_sync_);
				break;
			}
		}
	}

	return false;
}

void NeuralNetworkLab::onComboBox(WORD hi_word)
{
	if (IsWindowEnabled(expected_action_list_handle_))
	{
		switch (hi_word)
		{
			case CBN_SELENDOK:
			{
				vector<double> new_expected_outputs(action_strings_.size());
				LVITEM list_view_item;

				new_expected_outputs[
					SendMessage(expected_action_list_handle_, CB_GETCURSEL, 0, 0)
				] = 1.0;
				list_view_item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_STATE;
				list_view_item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
				list_view_item.lParam = 0;
				list_view_item.state = LVIS_SELECTED | LVIS_FOCUSED;
				list_view_item.pszText = LPSTR_TEXTCALLBACK;
				list_view_item.iSubItem = 0;
				EnterCriticalSection(&smith_sync_);
				list_view_item.iItem = current_example_index_ = static_cast<INT>(
					sample_inputs_.size()
				);
				sample_inputs_.push_back(current_inputs_);
				expected_outputs_.push_back(new_expected_outputs);
				ListView_InsertItem(training_sets_view_handle_, &list_view_item);

				for (size_t j = 0; j < current_inputs_.size(); ++j)
				{
					++list_view_item.iSubItem;
					SendMessage(
					    training_sets_view_handle_
					  , LVM_SETITEM
					  , 0
					  , reinterpret_cast<LPARAM>(&list_view_item)
					);
				}

				++list_view_item.iSubItem;
				list_view_item.pszText = _T("");
				SendMessage(
				    training_sets_view_handle_
				  , LVM_SETITEM
				  , 0
				  , reinterpret_cast<LPARAM>(&list_view_item)
				);

				for (size_t j = 0; j < new_expected_outputs.size(); ++j)
				{
					++list_view_item.iSubItem;
					list_view_item.pszText = LPSTR_TEXTCALLBACK;
					SendMessage(
					    training_sets_view_handle_
					  , LVM_SETITEM
					  , 0
					  , reinterpret_cast<LPARAM>(&list_view_item)
					);
				}

				LeaveCriticalSection(&smith_sync_);
				EnableWindow(expected_action_list_handle_, FALSE);
				InvalidateRect(outputs_handle_, 0, FALSE);
				updateVisibleListViews_();
				break;
			}
		}
	}
}

LRESULT NeuralNetworkLab::onListView(LPARAM l_param)
{
	NMHDR* p_nmhdr = reinterpret_cast<NMHDR*>(l_param);

	switch (p_nmhdr->code)
	{
		case LVN_KEYDOWN:
		{
			onKeyPress(reinterpret_cast<NMLVKEYDOWN*>(l_param)->wVKey);
			break;
		}

		case NM_CLICK:
		{
			NMITEMACTIVATE* p_nm_item = reinterpret_cast<NMITEMACTIVATE*>(l_param);
			HWND list_view_handle = p_nmhdr->hwndFrom;

			if (list_view_handle == training_sets_view_handle_)
			{
				// Ensure that a list-view item or sub-item was selected.
				LVHITTESTINFO hit_test_info;

				hit_test_info.pt = p_nm_item->ptAction;

				int row = ListView_SubItemHitTest(training_sets_view_handle_, &hit_test_info);

				if (-1 != row)
				{
					// Update AI.
					EnterCriticalSection(&smith_sync_);
					smith_.update(current_inputs_ = sample_inputs_[current_example_index_ = row]);
					LeaveCriticalSection(&smith_sync_);

					// Remove the blue from the focus rectangle.
					ListView_SetItemState(
					    training_sets_view_handle_
					  , current_example_index_
					  , LVIS_FOCUSED
					  , LVIS_SELECTED | LVIS_FOCUSED
					);

					// Request a repaint.
					InvalidateRect(inputs_view_handle_, 0, TRUE);
					InvalidateRect(training_sets_view_handle_, 0, FALSE);
					updateOutputsWindowAndExpectedAction_();
					return TRUE;
				}
			}
			else if (
			    (list_view_handle == inputs_view_handle_)
			 || (list_view_handle == init_params_view_handle_)
			)
			{
				int row = p_nm_item->iItem;

				if (-1 != row)
				{
					ListView_EditLabel(list_view_handle, row);
				}
			}

			break;
		}

		case LVN_ITEMACTIVATE:
		{
			NMITEMACTIVATE* p_nm_item = reinterpret_cast<NMITEMACTIVATE*>(l_param);
			int row = p_nm_item->iItem;

			if (-1 != row)
			{
				HWND list_view_handle = p_nmhdr->hwndFrom;

				if (list_view_handle == training_sets_view_handle_)
				{
					// Update AI.
					EnterCriticalSection(&smith_sync_);
					smith_.update(current_inputs_ = sample_inputs_[current_example_index_ = row]);
					LeaveCriticalSection(&smith_sync_);

					// Remove the blue from the focus rectangle.
					ListView_SetItemState(
					    training_sets_view_handle_
					  , current_example_index_
					  , LVIS_FOCUSED
					  , LVIS_SELECTED | LVIS_FOCUSED
					);

					// Request a repaint.
					InvalidateRect(inputs_view_handle_, 0, TRUE);
					InvalidateRect(training_sets_view_handle_, 0, FALSE);
					updateOutputsWindowAndExpectedAction_();
					return TRUE;
				}
				else if (
				    (list_view_handle == inputs_view_handle_)
				 || (list_view_handle == init_params_view_handle_)
				)
				{
					ListView_EditLabel(list_view_handle, row);
				}
			}

			break;
		}

		case LVN_ENDLABELEDIT:
		{
			NMLVDISPINFO* list_view_display_info = reinterpret_cast<NMLVDISPINFO*>(l_param);
			LVITEM& item = list_view_display_info->item;

			// Ensure that the user has not cancelled label editing.
			if (item.pszText)
			{
				HWND list_view_handle = p_nmhdr->hwndFrom;

				if (list_view_handle == inputs_view_handle_)
				{
					// Ensure that the user entered a valid, non-negative value.
					double value = _tstof(item.pszText);

					if (0.0 <= value)
					{
						EnterCriticalSection(&smith_sync_);
						current_inputs_[item.iItem] = value;
						smith_.update(current_inputs_);

						for (
							current_example_index_ = static_cast<INT>(sample_inputs_.size() - 1);
							-1 != current_example_index_;
							--current_example_index_
						)
						{
							if (current_inputs_ == sample_inputs_[current_example_index_])
							{
								ListView_SetItemState(
								    training_sets_view_handle_
								  , current_example_index_
								  , 0
								  , LVIS_SELECTED | LVIS_FOCUSED
								);
								break;
							}
						}

						LeaveCriticalSection(&smith_sync_);
						updateOutputsWindowAndExpectedAction_();
						updateVisibleListViews_();
						return TRUE;
					}
				}
				else  // if (list_view_handle == init_params_view_handle_)
				{
					switch (item.iItem)
					{
						case 0:
						{
							// Ensure that the user entered a valid, positive whole number.
							size_t hidden_neuron_count = 0;

							if (
							    _stscanf(item.pszText, _T("%u"), &hidden_neuron_count)
							 && (1 < hidden_neuron_count)
							 && (hidden_neuron_count < 9)
							)
							{
								EnterCriticalSection(&smith_sync_);
								NeuralNetworkGlobals::getInstance()->turnOff(
									NeuralNetworkGlobals::SHOW_NETWORK_TRAINING
								);
								current_hidden_neuron_count_ = hidden_neuron_count;

								if (isReady_())
								{
									initializeUserInterface_();
									updateOutputsWindowAndExpectedAction_();
									InvalidateRect(
									    training_sets_view_handle_
									  , 0
									  , FALSE
									);
								}

								LeaveCriticalSection(&smith_sync_);
								stopRun_();
								return TRUE;
							}

							break;
						}

						case 1:
						{
							// Ensure that the user entered a value in the range (0, 1).
							double learning_factor = _tstof(item.pszText);

							if ((0.01 < learning_factor) && (learning_factor < 1.00))
							{
								current_learning_factor_ = learning_factor;
								return TRUE;
							}

							break;
						}
					}
				}
			}

			return FALSE;
		}

		case NM_CUSTOMDRAW:
		{
			if (isReady_() && (p_nmhdr->hwndFrom == training_sets_view_handle_))
			{
				NMLVCUSTOMDRAW* list_view_custom_draw = reinterpret_cast<NMLVCUSTOMDRAW*>(l_param);

				switch (list_view_custom_draw->nmcd.dwDrawStage) 
			    {
					// Request notifications for individual list-view items
					// before the paint cycle begins.
					case CDDS_PREPAINT:
					{
						return CDRF_NOTIFYITEMDRAW;
					}

					// before an item is drawn
					case CDDS_ITEMPREPAINT:
					{
						return CDRF_NOTIFYSUBITEMDRAW;
					}

					// before a sub-item is drawn
					case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
					{
						unsigned char red = 255;
						unsigned char green = 0;
						unsigned char blue = 0;
						size_t const column = list_view_custom_draw->iSubItem;

						if (column && (column <= current_inputs_.size()))
						{
							red = 0;
							green = 127;
							blue = 255;
						}

						vector<double> outputs(action_strings_.size());
						DWORD_PTR index = list_view_custom_draw->nmcd.dwItemSpec;
						smith_.getNetwork().generateOutputs(sample_inputs_[index], outputs);

						if (
						    MlpAgent::computeIndexOfMaxOutput(outputs)
						 == MlpAgent::computeIndexOfMaxOutput(expected_outputs_[index])
						)
						{
							red = 0;
							green = 255;
						}

						if (current_example_index_ == index)
						{
							list_view_custom_draw->clrText = RGB(0, 0, 0);
							list_view_custom_draw->clrTextBk = RGB(red, green, blue);
						}
						else
						{
							list_view_custom_draw->clrText = RGB(red, green, blue);
							list_view_custom_draw->clrTextBk = RGB(0, 0, 0);
						}

						return CDRF_NEWFONT;
					}
				}
			}

			return CDRF_DODEFAULT;
		}

		case LVN_GETDISPINFO:
		{
			NMLVDISPINFO* list_view_display_info = reinterpret_cast<NMLVDISPINFO*>(l_param);
			LVITEM& item = list_view_display_info->item;
			HWND list_view_handle = p_nmhdr->hwndFrom;
			size_t row = list_view_display_info->item.iItem;
			size_t column = list_view_display_info->item.iSubItem;

			if (list_view_handle == training_sets_view_handle_)
			{
				if (!isReady_())
				{
					return FALSE;
				}

				if (column == 0)
				{
					item.pszText = select_me_text_;
				}
				else if (column <= input_strings_.size())
				{
					_stprintf(
					    item.pszText
					  , _T("%1.6f")
					  , sample_inputs_[row][column - 1]
					);
				}
				else if (input_strings_.size() < --column)
				{
					_stprintf(
					    item.pszText
					  , _T("%1.6f")
					  , expected_outputs_[row][--column - input_strings_.size()]
					);
				}
			}
			else if (list_view_handle == inputs_view_handle_)
			{
				if (!isReady_())
				{
					return FALSE;
				}

				switch (column)
				{
					case 0:
					{
						_stprintf(item.pszText, _T("%1.6f"), current_inputs_[row]);
						break;
					}

					case 1:
					{
						_stprintf(item.pszText, _T("%s"), input_strings_[row].c_str());
						break;
					}
				}
			}
			else if (list_view_handle == hidden_neurons_view_handle_)
			{
				if (!isReady_())
				{
					return FALSE;
				}

				Neuron const& neuron = smith_.getNetwork().getHiddenLayer().getNeurons()[row];

				switch (column)
				{
					case 0:
					{
						_stprintf(item.pszText, _T("%u"), row);
						break;
					}

					case 1:
					{
						_stprintf(item.pszText, _T("%+1.6f"), neuron.getBias());
						break;
					}

					default:
					{
						_stprintf(item.pszText, _T("%+1.6f"), neuron.getWeights()[column - 2]);
						break;
					}
				}
			}
			else if (list_view_handle == output_neurons_view_handle_)
			{
				if (!isReady_())
				{
					return FALSE;
				}

				Neuron const& neuron = smith_.getNetwork().getOutputLayer().getNeurons()[row];

				switch (column)
				{
					case 0:
					{
						_stprintf(item.pszText, _T("%u"), row);
						break;
					}

					case 1:
					{
						_stprintf(item.pszText, _T("%+1.6f"), neuron.getBias());
						break;
					}

					default:
					{
						_stprintf(item.pszText, _T("%+1.6f"), neuron.getWeights()[column - 2]);
						break;
					}
				}
			}
			else if (list_view_handle == init_params_view_handle_)
			{
				switch (row)
				{
					case 0:
					{
						switch (column)
						{
							case 0:
							{
								_stprintf(item.pszText, _T("%i"), current_hidden_neuron_count_);
								break;
							}

							case 1:
							{
								_stprintf(item.pszText, _T("%s"), hidden_neuron_count_param_text_);
								break;
							}
						}

						break;
					}

					case 1:
					{
						switch (column)
						{
							case 0:
							{
								_stprintf(item.pszText, _T("%1.6f"), current_learning_factor_);
								break;
							}

							case 1:
							{
								_stprintf(item.pszText, _T("%s"), learning_factor_param_text_);
								break;
							}
						}

						break;
					}
				}
			}

			break;
		}
	}

	return FALSE;
}

void NeuralNetworkLab::onSize(HWND window_handle, WPARAM w_param, LPARAM l_param)
{
	SCROLLINFO horizontal_scroll_info;
	SCROLLINFO vertical_scroll_info;

	// Get the client dimensions and set the scrollbar properties.
	horizontal_scroll_info.cbSize = vertical_scroll_info.cbSize = sizeof(SCROLLINFO);
	horizontal_scroll_info.fMask = vertical_scroll_info.fMask = (
	    SIF_RANGE
	  | SIF_PAGE
	  | SIF_DISABLENOSCROLL
	);
	horizontal_scroll_info.nMin = vertical_scroll_info.nMin = 0;
	horizontal_scroll_info.nPage = LOWORD(l_param);
	vertical_scroll_info.nPage = HIWORD(l_param);
	horizontal_scroll_info.nMax = outputs_width_ - 1;
	vertical_scroll_info.nMax = outputs_height_ - 1;

	// Enable the horizontal scroll bar.
	SetScrollInfo(window_handle, SB_HORZ, &horizontal_scroll_info, TRUE);

	// Enable the vertical scroll bar.
	SetScrollInfo(window_handle, SB_VERT, &vertical_scroll_info, TRUE);
}

void NeuralNetworkLab::onHScroll(HWND window_handle, WPARAM w_param, LPARAM l_param)
{
	SCROLLINFO horizontal_scroll_info;

	// Get all information pertaining to the horizontal scroll bar.
	horizontal_scroll_info.cbSize = sizeof(SCROLLINFO);
	horizontal_scroll_info.fMask  = SIF_ALL;
	GetScrollInfo(window_handle, SB_HORZ, &horizontal_scroll_info);

	// Save the old position for later comparison.
	int const old_position = horizontal_scroll_info.nPos;

	// Adjust the scroll position based upon the scroll request.
	switch (LOWORD(w_param))
	{
		case SB_LEFT:
			horizontal_scroll_info.nPos = horizontal_scroll_info.nMin;
			break;
		case SB_RIGHT:
			horizontal_scroll_info.nPos = horizontal_scroll_info.nMax;
			break;
		case SB_LINELEFT:
			horizontal_scroll_info.nPos -= 1;
			break;
		case SB_LINERIGHT:
			horizontal_scroll_info.nPos += 1;
			break;
		case SB_PAGELEFT:
			horizontal_scroll_info.nPos -= horizontal_scroll_info.nPage;
			break;
		case SB_PAGERIGHT:
			horizontal_scroll_info.nPos += horizontal_scroll_info.nPage;
			break;
		case SB_THUMBTRACK:
			horizontal_scroll_info.nPos = horizontal_scroll_info.nTrackPos;
			break;
	}

	// Set the new, horizontal scroll position.
	horizontal_scroll_info.fMask = SIF_POS;
	SetScrollInfo(window_handle, SB_HORZ, &horizontal_scroll_info, TRUE);
	GetScrollInfo(window_handle, SB_HORZ, &horizontal_scroll_info);

	if (old_position != horizontal_scroll_info.nPos)
	{
		outputs_offset_.x = -horizontal_scroll_info.nPos;
		InvalidateRect(outputs_handle_, 0, FALSE);
		updateVisibleListViews_();
	}
}

void NeuralNetworkLab::onVScroll(HWND window_handle, WPARAM w_param, LPARAM l_param)
{
	SCROLLINFO vertical_scroll_info;

	// Get all information pertaining to the vertical scroll bar.
	vertical_scroll_info.cbSize = sizeof(SCROLLINFO);
	vertical_scroll_info.fMask  = SIF_ALL;
	GetScrollInfo(window_handle, SB_VERT, &vertical_scroll_info);

	// Save the old position for later comparison.
	int const old_position = vertical_scroll_info.nPos;

	// Adjust the scroll position based upon the scroll request.
	switch (LOWORD(w_param))
	{
		case SB_LEFT:
			vertical_scroll_info.nPos = vertical_scroll_info.nMin;
			break;
		case SB_RIGHT:
			vertical_scroll_info.nPos = vertical_scroll_info.nMax;
			break;
		case SB_LINELEFT:
			vertical_scroll_info.nPos -= 1;
			break;
		case SB_LINERIGHT:
			vertical_scroll_info.nPos += 1;
			break;
		case SB_PAGELEFT:
			vertical_scroll_info.nPos -= vertical_scroll_info.nPage;
			break;
		case SB_PAGERIGHT:
			vertical_scroll_info.nPos += vertical_scroll_info.nPage;
			break;
		case SB_THUMBTRACK:
			vertical_scroll_info.nPos = vertical_scroll_info.nTrackPos;
			break;
	}

	// Set the new, vertical scroll position.
	vertical_scroll_info.fMask = SIF_POS;
	SetScrollInfo(window_handle, SB_VERT, &vertical_scroll_info, TRUE);
	GetScrollInfo(window_handle, SB_VERT, &vertical_scroll_info);

	if (old_position != vertical_scroll_info.nPos)
	{
		outputs_offset_.y = -vertical_scroll_info.nPos;
		InvalidateRect(outputs_handle_, 0, FALSE);
		updateVisibleListViews_();
	}
}

void NeuralNetworkLab::onFileOpen(HWND window_handle)
{
	OPENFILENAME open_file_name;
	TCHAR        file_name[MAX_BUFFER_SIZE];
	TCHAR        initial_directory[MAX_BUFFER_SIZE];

	ZeroMemory(&open_file_name, sizeof(open_file_name));
	open_file_name.lStructSize = sizeof(open_file_name);
	open_file_name.hwndOwner = window_handle;
	open_file_name.lpstrFile = file_name;

	// Set lpstrFile[0] to '\0' so that GetOpenFileName does not 
	// use the contents of szFileName to initialize itself.
	open_file_name.lpstrFile[0] = _T('\0');
	open_file_name.nMaxFile = sizeof(file_name);
	open_file_name.lpstrFilter = text_filter_;
	open_file_name.nFilterIndex = 1;
	open_file_name.lpstrFileTitle = 0;
	open_file_name.nMaxFileTitle = 0;
	open_file_name.lpstrInitialDir = initial_directory;
	_stprintf(initial_directory, _T("%s"), _T(".\\Data"));
	open_file_name.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

	if (GetOpenFileName(&open_file_name))
	{
		openFile_(window_handle, file_name);
	}
}

void NeuralNetworkLab::paintLabels(HDC device_context_handle) const
{
	TCHAR buffer[MAX_BUFFER_SIZE];
	int old_mode = SetBkMode(device_context_handle, TRANSPARENT);
	COLORREF old_color = SetTextColor(device_context_handle, RGB(0, 0, 0));

	_stprintf(buffer, _T("%s"), _T("Correct Action:"));
	TextOut(
	    device_context_handle
	  , expected_action_label_x_
	  , expected_action_label_y_
	  , buffer
	  , lstrlen(buffer)
	);
	SetTextColor(device_context_handle, old_color);
	SetBkMode(device_context_handle, old_mode);
}

void NeuralNetworkLab::paintOutputs_(HDC device_context_handle, HDC buffer_context_handle) const
{
	LONG const OUTPUT_COUNT = static_cast<LONG>(action_strings_.size());

	if (OUTPUT_COUNT == 0)
	{
		return;
	}

	LONG const CHAR_WIDTH = 8;
	LONG const CHAR_HEIGHT = 16;
	LONG const SAMPLE_COUNT = static_cast<LONG>(sample_inputs_.size());
	MlpNetwork const& network = smith_.getNetwork();
	LONG const X_OFFSET_0 = outputs_offset_.x + CHAR_WIDTH;

	TCHAR buffer[MAX_BUFFER_SIZE];
	COLORREF old_bk_color = SetBkColor(buffer_context_handle, RGB(0, 0, 0));
	COLORREF old_color = SetTextColor(buffer_context_handle, RGB(255, 255, 255));

	_stprintf(
	    buffer
	  , _T("%s")
	  , _T("You can edit the values on the left, or you can press either play button to train the agent.")
	);
	TextOut(
	    buffer_context_handle
	  , X_OFFSET_0
	  , outputs_offset_.y
	  , buffer
	  , lstrlen(buffer)
	);
	_stprintf(
	    buffer
	  , _T("%s %u")
	  , _T("Iteration cycle:")
	  , iteration_cycle_
	);

	LONG const Y_OFFSET_0 = outputs_offset_.y + (CHAR_HEIGHT << 1);

	TextOut(buffer_context_handle, X_OFFSET_0, Y_OFFSET_0, buffer, lstrlen(buffer));

	vector<double> const& outputs = smith_.getCurrentOutputs();

	EnterCriticalSection(&smith_sync_);

	if (-1 == current_example_index_)
	{
		LONG const CURRENT_OUTPUT = static_cast<LONG>(MlpAgent::computeIndexOfMaxOutput(outputs));
		LONG i = 0;

		while (i < OUTPUT_COUNT)
		{
			if (i == CURRENT_OUTPUT)
			{
				SetTextColor(buffer_context_handle, RGB(127, 255, 127));
			}
			else
			{
				SetTextColor(buffer_context_handle, RGB(255, 127, 127));
			}

			_stprintf(
			    buffer
			  , _T("%s %s %+1.6f")
			  , action_strings_[i].c_str()
			  , _T(" tendency is ")
			  , outputs[i]
			);
			TextOut(
			    buffer_context_handle
			  , X_OFFSET_0
			  , ++i * CHAR_HEIGHT + Y_OFFSET_0
			  , buffer
			  , lstrlen(buffer)
			);
		}

		SetTextColor(buffer_context_handle, RGB(127, 255, 127));
	}
	else
	{
		vector<double>
			output_errors(OUTPUT_COUNT);
		vector<double> const&
			expected_output = expected_outputs_[current_example_index_];
		LONG const
			CORRECT_OUTPUT = static_cast<LONG>(MlpAgent::computeIndexOfMaxOutput(expected_output));

		MlpNetwork::computeOutputErrors(expected_output, outputs, output_errors);

		LONG i = 0;

		while (i < OUTPUT_COUNT)
		{
			if (i == CORRECT_OUTPUT)
			{
				SetTextColor(buffer_context_handle, RGB(127, 255, 127));
			}
			else
			{
				SetTextColor(buffer_context_handle, RGB(255, 127, 127));
			}

			_stprintf(
			    buffer
			  , _T("%s %s %+1.6f %s %+1.6f")
			  , action_strings_[i].c_str()
			  , _T(" tendency is ")
			  , outputs[i]
			  , _T(" with an error of ")
			  , output_errors[i]
			);
			TextOut(
			    buffer_context_handle
			  , X_OFFSET_0
			  , ++i * CHAR_HEIGHT + Y_OFFSET_0
			  , buffer
			  , lstrlen(buffer)
			);
		}

		if (CORRECT_OUTPUT == static_cast<LONG>(smith_.getCurrentActionIndex()))
		{
			SetTextColor(buffer_context_handle, RGB(127, 255, 127));
		}
		else
		{
			SetTextColor(buffer_context_handle, RGB(255, 127, 127));
		}
	}

	LONG const X_OFFSET_1 = (outputs_width_ >> 1) + X_OFFSET_0;
	LONG const Y_OFFSET_1 = (OUTPUT_COUNT + 2) * CHAR_HEIGHT + Y_OFFSET_0;
	size_t action_index = smith_.getCurrentActionIndex();

	assert(
	    (action_index < action_strings_.size())
	 && "You did not initialize or update the agent's action correctly."
	);
	_stprintf(
	    buffer
	  , _T("%s %s")
	  , _T("Current action is ")
	  , action_strings_[action_index].c_str()
	);
	TextOut(
	    buffer_context_handle
	  , X_OFFSET_0
	  , Y_OFFSET_1
	  , buffer
	  , lstrlen(buffer)
	);

	if (-1 != current_example_index_)
	{
		action_index = MlpAgent::computeIndexOfMaxOutput(
			expected_outputs_[current_example_index_]
		);
		SetTextColor(buffer_context_handle, RGB(127, 255, 127));
		_stprintf(
		    buffer
		  , _T("%s %s")
		  , _T("Correct action is ")
		  , action_strings_[action_index].c_str()
		);
		TextOut(
		    buffer_context_handle
		  , X_OFFSET_1
		  , Y_OFFSET_1
		  , buffer
		  , lstrlen(buffer)
		);
	}

	// Render appropriate image[s].
	HDC action_device_context_handle = CreateCompatibleDC(buffer_context_handle);
	BITMAP action_bitmap;
	HBITMAP action_bitmap_handle, old_bitmap_handle;

	if (action_bitmap_handle = action_bitmap_handles_[smith_.getCurrentActionIndex()])
	{
		GetObject(action_bitmap_handle, sizeof(BITMAP), &action_bitmap);
		old_bitmap_handle = reinterpret_cast<HBITMAP>(
			SelectObject(action_device_context_handle, action_bitmap_handle)
		);
		BitBlt(
		    buffer_context_handle
		  , X_OFFSET_0
		  , Y_OFFSET_1 + (CHAR_HEIGHT << 1)
		  , action_bitmap.bmWidth
		  , action_bitmap.bmHeight
		  , action_device_context_handle
		  , 0
		  , 0
		  , SRCPAINT
		);
		SelectObject(action_device_context_handle, old_bitmap_handle);
	}

	if (
	    (-1 != current_example_index_)
	 && (
	        action_bitmap_handle
	      = action_bitmap_handles_[
	            MlpAgent::computeIndexOfMaxOutput(expected_outputs_[current_example_index_])
	        ]
	    )
	)
	{
		GetObject(action_bitmap_handle, sizeof(BITMAP), &action_bitmap);
		old_bitmap_handle = reinterpret_cast<HBITMAP>(
			SelectObject(action_device_context_handle, action_bitmap_handle)
		);
		BitBlt(
		    buffer_context_handle
		  , X_OFFSET_1
		  , Y_OFFSET_1 + (CHAR_HEIGHT << 1)
		  , action_bitmap.bmWidth
		  , action_bitmap.bmHeight
		  , action_device_context_handle
		  , 0
		  , 0
		  , SRCPAINT
		);
		SelectObject(action_device_context_handle, old_bitmap_handle);
	}

	DeleteDC(action_device_context_handle);
	LeaveCriticalSection(&smith_sync_);
	SetTextColor(buffer_context_handle, old_color);
	SetBkColor(buffer_context_handle, old_bk_color);

	// Copy from back buffer to front buffer.
	BitBlt(
	    device_context_handle
	  , 0
	  , 0
	  , outputs_window_width_
	  , outputs_window_height_
	  , buffer_context_handle
	  , 0
	  , 0
	  , SRCCOPY
	);
}

void NeuralNetworkLab::paintOutputs(HDC device_context_handle) const
{
	HDC buffer_context_handle = CreateCompatibleDC(device_context_handle);
	HBITMAP buffer_bitmap_handle = CreateCompatibleBitmap(
	    device_context_handle
	  , outputs_window_width_
	  , outputs_window_height_
	);
	HGDIOBJ old_bitmap_handle = SelectObject(buffer_context_handle, buffer_bitmap_handle);

	paintOutputs_(device_context_handle, buffer_context_handle);
	SelectObject(buffer_context_handle, old_bitmap_handle);
	DeleteObject(buffer_bitmap_handle);
	DeleteDC(buffer_context_handle);
}

int
	APIENTRY _tWinMain(
	    HINSTANCE application_handle
	  , HINSTANCE previous_application_handle
	  , LPTSTR    command_line
	  , int       n_cmd_show
	)
{
	UNREFERENCED_PARAMETER(previous_application_handle);
	UNREFERENCED_PARAMETER(command_line);

	// Perform application initialization:
	if (!NeuralNetworkLab::getInstance()->initializeApplication(application_handle, n_cmd_show))
	{
		NeuralNetworkLab::deleteInstance();
		NeuralNetworkGlobals::deleteInstance();
		return FALSE;
	}

	MSG msg;
	HACCEL accelerator_table_handle = LoadAccelerators(
	    application_handle
	  , MAKEINTRESOURCE(IDC_NEURALNETWORK)
	);

	// Main message loop:
	while (GetMessage(&msg, 0, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, accelerator_table_handle, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	NeuralNetworkLab::deleteInstance();
	NeuralNetworkGlobals::deleteInstance();
	return static_cast<int>(msg.wParam);
}

